//
//  MySQLConnection.m
//  LibMySQL
//
//  Created by Marcus Grenängen on 2009-07-16.
//  Copyright 2009 SneWs Soft. All rights reserved.

#import "MySQLConnection.h"

/// Defines our private (hidden) methods for managing MySQL
/// 
@interface MySQLConnection( MYSQL_SPECIFIC )
- (BOOL)_setupIfNeeded;
- (BOOL)_teardownIfNeeded;
- (BOOL)_realConnect;
- (MySQLException *)getExceptionWithCurrentState;
- (void)raiseExceptionIfNeeded;

// Fetch helpers
- (MYSQL_RES *)_runQuery:(NSString *)sql;
@end

@interface MySQLConnection (Private)
@property(nonatomic, readwrite, copy) NSString *hostname;
@property(nonatomic, readwrite, assign) NSInteger port;
@property(nonatomic, readwrite, copy) NSString *username;
@property(nonatomic, readwrite, copy) NSString *password;
@property(nonatomic, readwrite, assign) NSInteger timeout;
@property(nonatomic, readwrite, assign) unsigned long connectionProperties;
@property(nonatomic, readwrite, copy) NSString *socket;

- (void)cleanup;
@end


#if MYSQL_VERSION_ID >= 50136
@interface MySQLParameter( MySQLConnection_Customization )
- (unsigned int)_guessDataType;
+ (NSArray*)_parseArgListWithCommands:(MySQLParameter *)firstObject withList:(va_list)l;
@end
#endif


@implementation MySQLConnection

#pragma mark -
#pragma mark Properties, GET / SET
@synthesize hostname;
@synthesize database;
@synthesize username;
@synthesize password;
@synthesize port;
@synthesize timeout;
@synthesize connectionProperties;
@synthesize charset;
@synthesize socket;

#pragma mark -
#pragma mark Static Methods
/*
 * @description: Creates a new MySQLConnection based on the connectionString data.
 *				 
 * @connectionString: Supported formats for the connection string is as follows:
 *
 *	Standard connection string: 
 *		Server=myServerAddress;Port=3306;Database=myDataBase;Uid=myUsername;Pwd=myPassword;
 *
 *	Named pipes: 
 *		Server=myServerAddress;Port=-1;Database=myDataBase;Uid=myUsername;Pwd=myPassword;
 *
 *	With charset: 
 *		Server=myServerAddress;Port=3306;Database=myDataBase;Uid=myUsername;Pwd=myPassword;Charset=utf8;
 *
 */
+ (MySQLConnection *)connectionWithString:(NSString *)connectionString {
	if (connectionString == nil || [connectionString length] < 1)
		return nil;
	
	NSString	*oServer		= nil;
	NSString	*oUid			= nil;
	NSString	*oPwd			= nil;
	NSInteger	tmpPort			= 3306;
	NSString	*oDatabase		= nil;
	NSString	*oCharset		= nil;
	
	NSString	*key			= nil;
	NSString	*value			= nil;
	NSString	*section		= nil;
	
	NSArray *parts = [connectionString componentsSeparatedByString: @";"];
	for (section in parts) {
		// Get the sub part, Key=Value
		NSArray *keyValue = [section componentsSeparatedByString: @"="];
		if (keyValue == nil || [keyValue count] != 2) {
			// Not a valid part... skip
			[keyValue release];
			continue;
		
		} // END if( keyValue == nil || [keyValue count] != 2 )
		
		// Get the key and value and parse key to get the correct value
		/// TODO: Better error handling
		key = [[keyValue objectAtIndex: 0] stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]];
		value = [[keyValue objectAtIndex: 1] stringByTrimmingCharactersInSet: [NSCharacterSet whitespaceCharacterSet]];
		if( [key compare: @"Server" options: NSCaseInsensitiveSearch] == NSOrderedSame )
			oServer = value;
		else if( [key compare: @"Port" options: NSCaseInsensitiveSearch] == NSOrderedSame ) {
			@try {
				tmpPort = [value integerValue];
			}
			@catch ( NSException *error ) {
				tmpPort = 3306;
				[error release];
			}
		
		} // END else if( [key compare: @"Port" options: NSCaseInsensitiveSearch] == NSOrderedSame )
		else if( [key compare: @"Uid" options: NSCaseInsensitiveSearch] == NSOrderedSame )
			oUid = value;
		else if( [key compare: @"Pwd" options: NSCaseInsensitiveSearch] == NSOrderedSame )
			oPwd = value;
		else if( [key compare: @"Database" options: NSCaseInsensitiveSearch] == NSOrderedSame )
			oDatabase = value;
		else if( [key compare: @"Charset" options: NSCaseInsensitiveSearch] == NSOrderedSame )
			oCharset = value;
		
		[keyValue release];
		
	} // END for( NSString* section in parts )
	
	[parts release];
	[key release];
	[value release];
	[section release];
	
	// Valid server?
	if( oServer != nil ) {
		MySQLConnection* retVal = [[MySQLConnection alloc] initWithHost: oServer
																   port: tmpPort
																   user: oUid
															   password: oPwd];
		[retVal open];
		
		if( oCharset != nil )
			[retVal changeCharacterSet: oCharset];
		
		if( oDatabase != nil )
			[retVal changeDatabase: oDatabase];
		
		// Cleanup
		[oServer release];
		[oUid release];
		[oPwd  release];
		[oDatabase release];
		[oCharset release];
		
		return retVal;
	
	} // END if( server != nil )
	
	return nil;

} // END METHOD: withConnectionString: 

/*
 * @description: Creates a new MySQLConnection based on the dictionary
 *				 
 * @oDictionary: Supported key/value for the dictionary is as follows:
 *
 *	For possible keys. Please see MySQLGlobals.h and the kMySQLConnection... section
 *
 *  Keys:Values for standard connection:
 *		Server: myServerAddress (NSString)
 *		Port: 3306 (NSNumber)
 *		Database: myDatabase (NSString)
 *		Uid: myUserName (NSString)
 *		Pwd: myPassword (NSString)
 *		Charset: utf8 (NSString)
 *
 *	Keys:Values for named pipes:
 *		Server: myServerAddress (NSString)
 *		Port: -1 (NSNumber)
 *		Database: myDatabase (NSString)
 *		Uid: myUserName (NSString)
 *		Pwd: myPassword (NSString)
 */
+ (MySQLConnection *)connectionWithDictionary: (NSDictionary *)oDictionary {
	MySQLConnection *oConnection = nil;
	NSString		*oServer;
	NSString		*oUid;
	NSString		*oPwd;
	NSInteger		oPort;
	NSString		*oDatabase;
	NSString		*oCharset;
	
	if (oDictionary == nil)	// sanity check
		return nil;
	
	oServer		= [oDictionary valueForKey: kMySQLConnectionServer];
	oUid		= [oDictionary valueForKey: kMySQLConnectionUser];
	oPwd		= [oDictionary valueForKey: kMySQLConnectionPassword];
	oDatabase	= [oDictionary valueForKey: kMySQLConnectionDatabase];
	oCharset	= [oDictionary valueForKey: kMySQLConnectionCharset];
	
	@try {
		oPort	= [[oDictionary valueForKey: kMySQLConnectionPort] integerValue];
	}
	@catch( NSException *error ) {
		oPort = 3306;
		[error release];
	}
	
	if (oServer != nil) {	// valid server?
		oConnection = [[MySQLConnection alloc] initWithHost: oServer port: oPort user: oUid password: oPwd];
		
		[oConnection open];
		[oConnection changeCharacterSet: oCharset];
		[oConnection changeDatabase: oDatabase];
	
	} // END if (oServer != nil)
	
	[oServer release];
	[oUid release];
	[oPwd release];
	[oDatabase release];
	[oCharset release];
	
	return [oConnection autorelease];
	
} // END METHOD: connectionWithDictionary: 

#pragma mark -
#pragma mark Init methods
- (id)init {
	return [self initWithHost: @"" port: 3306 user: @"" password: @"" options: 0];
	
} // END METHOD: init: 

- (id)initWithHost: (NSString*)h port: (NSInteger)p {
	return [self initWithHost: h port: p user: @"" password: @"" options: 0];
	
} // END METHOD: initWithHost: 

- (id) initWithHost: (NSString*)h port: (NSInteger)p user: (NSString*)u password: (NSString*)pwd {
	return [self initWithHost: h port: p user: u password: pwd options: 0];
	
} // END METHOD: initWithHost: 

- (id) initWithHost: (NSString*)h port: (NSInteger)p user: (NSString*)u password: (NSString*)pwd options: (NSInteger)properties {
	if (self = [super init]) {
		_mysql = nil;
		hostname = [h copy];
		port = p;
		database = @"mysql";
		username = [u copy];
		password = [pwd copy];
		timeout = 30; // value in seconds
		connectionProperties = properties;
		charset = @"utf8";
		socket = nil;
		
	} // END if( ( self = [super init] ) )
	
	return self;

} // END METHOD: initWithHost: 

- (id) initWithSocket:(NSString*)s user:(NSString*)u password:(NSString*)pwd options:(NSInteger)properties {
	if (self = [self initWithHost: @"" port: -1 user: u password: pwd options: properties])
		socket = [s copy];
	
	return self;

} // END METHOD: initWithSocket: 

#pragma mark -
#pragma mark Dealloc
/*
 * @description: Used when running without GC.
 *				 Deallocates this connection, and it will close any 
 *				 open connection.
 */
- (void)dealloc {	
	[self cleanup];
	[super dealloc];

} // END METHOD: dealloc: 

/*
 * @description: Used when running in GC enabled mode.
 *				 Performs cleanup and closes the conneciton if it's open
 */
- (void)finalize {
	[self cleanup];
	[super finalize];

} // END METHOD: finalize: 

- (void)cleanup {
	// close if needed
	[self close];
	
	[hostname release];
	[username release];
	[password release];
	[database release];
	[charset release];
	[socket release];

} // END METHOD: cleanup: 


#pragma mark -
#pragma mark Public Methods
/*
 * @description: Opens the connection to the MySQL server.
 *
 * @throws: A MySQLException is thrown if the connection fails.
 */
- (void)open {	
	// Setup the MySQL stuff if needed.
	if( ![self _setupIfNeeded] )
		[self raiseExceptionIfNeeded];
	
	// Let's perform the connect then...
	if( ![self _realConnect] )
		[self raiseExceptionIfNeeded];
	
} // END METHOD: open:

/*
 * @description: Returns TRUE if the connection is open; 
 *				 Otherwise FALSE;
 *
 */
- (BOOL)isOpen {
	if( _mysql != NULL )
		return TRUE;
	
	return FALSE;
	
} // END METHOD: isOpen: 


/*
 * @description: Closes the connection to the MySQL server if it's open.
 */
- (void)close {
	if( ![self _teardownIfNeeded] )
		[self raiseExceptionIfNeeded];
	
} // END METHOD: close: 

/*
 * @description: Returns a NSString* describing the version for the server we are connected to.
 * @throws: A MySQLException.
 */
- (NSString*)serverVersion {
	return [self executeScalar:@"SELECT VERSION() AS V0;"];
	
} // END METHOD: serverVersion:

/*
 * @description: Changes the active database for this connection
 * @throws: A MySQLException is throws if the database not could be changed.
 */
- (void)changeDatabase:(NSString*)newDatabaseName {
	[self open];
	
	if( database != newDatabaseName ) {
		[database release];
		database = [newDatabaseName copy];
		
		if( !mysql_select_db( _mysql, [database UTF8String] ) )
			[self raiseExceptionIfNeeded];
	
	} // END if (database != newDatabaseName)
	
} // END METHOD: changeDatabase: 

/*
 * @description: Changes the current user and database.
 *				 NOTE: IF the database parameter is nil or empty it will try to 
 *					   use the already exsisting database name if one is available.
 *
 * @throws: A MySQLException is throws if the login fails or that the database isn't valid.
 */
- (void)changeUser:(NSString *)user withPassword:(NSString *)pwd usingDatabase:(NSString *)db {
	[self close];
	
	if( username != user ) {
		[username release];
		username = [user copy];
	}
	
	if( password != pwd ) {
		[password release];
		password = [pwd copy];
	}
	
	if( db != nil && [db length] > 0 ) {
		if( database != db ) {
			[database release];
			database = [db copy];
		
		} // END if( database != db )
	
	} // END if( db != nil && [db length] > 0 )
	
	[self open];

} // END METHOD: changeUser: 

/*
 * @description: Changes the character set currently in use.
 * @throws: A MySQLException is thrown if the character set not could be changed
 */
- (void)changeCharacterSet:(NSString *)charsetByName {
	if( charsetByName == nil || [charsetByName length] < 1 ) {
		NSException* invalidOperation = [[NSException alloc] initWithName: @"Invalid Operation" reason: @"Charset can't be nil" userInfo: nil];
		@throw invalidOperation;
		
	} // END if( charsetByName == nil || [charsetByName length] < 1 )
	
	if( charset != charsetByName ) {
		[charset release];
		
		charset = [charsetByName copy];
		if( _mysql != NULL ) {
			mysql_set_character_set( _mysql, [charset UTF8String] );
			[self raiseExceptionIfNeeded];
		}
		
	} // END if (charsetByName != charset)
	
	
} // END METHOD: changeCharacterSet: 

/*
 * @description: Changes the character set currently in use.
 * @throws: A MySQLException is thrown if the encoding can't be init'ed.
 */
- (NSStringEncoding)encodingForConnection {
	NSStringEncoding retVal = MySQLConnection_DefaultEncoding;
	
	// Basic check if we just should return default encoding or try to
	// find one based on user spec's...
	if( charset == nil || [charset length] < 4 )
		return retVal;
	
	if( [charset compare: @"latin1"] == NSOrderedSame )
		retVal = NSISOLatin1StringEncoding;
	else if( [charset compare: @"latin2"] == NSOrderedSame )
		retVal = NSISOLatin2StringEncoding;
	else if( [charset compare: @"win1250"] == NSOrderedSame )
		retVal = NSWindowsCP1250StringEncoding;
	else if( [charset compare: @"win1251"] == NSOrderedSame )
		retVal = NSWindowsCP1251StringEncoding;
	else if( [charset compare: @"utf8"] == NSOrderedSame )
		retVal = NSUTF8StringEncoding;
	else if( [charset compare: @"euc_kr"] == NSOrderedSame )
		retVal = NSJapaneseEUCStringEncoding;
	else if( [charset compare: @"sjis"] == NSOrderedSame )
		retVal = NSShiftJISStringEncoding;
		
	return retVal;

} // END METHOD: encodingForConnection: 

/*
 * @description: Gets the last inserted ID for the active database and table
 *				 using the AUTO_INCREMENT value.
 *
 * @returns: The last inserted id > 0; Or if not successfull a value < 1
 *
 * @throws: A MySQLException.
 */
- (NSInteger)lastInsertedId {
	NSInteger retVal = -1L;
	
	MYSQL_RES *result = [self _runQuery: @"SELECT LAST_INSERT_ID();"];
	int       numberOfRows;
	MYSQL_ROW row;
	
	if( result != NULL ) {
		numberOfRows = mysql_num_rows( result );
		if( numberOfRows > 0 ) {
			row = mysql_fetch_row( result );
			if( row != NULL && row[0] != NULL )
				retVal = (NSInteger)[MySQLResult convertToObjCValue: row[0] 
													  fromMySQLType: MYSQL_TYPE_LONGLONG 
														  withFlags: 0 
													  usingEncoding: [self encodingForConnection] ];
		} // END if (numberOfRows > 0)
		
	} // END if (result != NULL)
	
	// Cleanup
	mysql_free_result(result);
	[self close];
	
	return retVal;
	
} // END METHOD: lastInsertedId:

#pragma mark -
#pragma mark MySQL information extract methods
- (NSString *)clientInformation {
	[self open];
	const char *info = mysql_get_client_info();
	if( info != NULL )
		return [NSString stringWithCString: info 
								  encoding: [self encodingForConnection]];
	
	return nil;
	
} // END METHOD: clientInformation: 

- (NSString *)hostInformation {
	[self open];
	const char *info = mysql_get_host_info( _mysql );
	if( info != NULL )
		return [NSString stringWithCString: info 
								  encoding: [self encodingForConnection]];
	
	return nil;
	
} // END METHOD: hostInformation: 


- (NSString *)serverInformation {
	[self open];
	const char *info = mysql_get_server_info( _mysql );
	if( info != NULL )
		return [NSString stringWithCString: info 
								  encoding: [self encodingForConnection]];
	
	return nil;
	
} // END METHOD: serverInformation: 


#pragma mark -
#pragma mark Methods to execute diffrent SQL actions.
/*
 * @description: Run a SQL statement that returns no data.
 *				 Like UPDATE or DELETE statements.
 *
 * @returns: A value representing the number of affected rows.
 */
- (NSInteger)executeNonQuery:(NSString*)sqlCommand {
	MYSQL_RES * result = [self _runQuery: sqlCommand];
	my_ulonglong rows = -1L;
	
	rows = mysql_affected_rows( _mysql );
	
	// Note, a non query will not return any results, BUT
	// we still check for results so that we can cleanup after a
	// badly sent query.
	if( result != NULL ) {		
		mysql_free_result( result );
		[self close];
		
	} // END if (result != NULL)
	
	return (NSInteger)rows;
	
} // END METHOD: executeNonQuery:

/*
 * @description: Run a SQL statement that will return a single row single column
 *				 value. The value will be returned as it's native Obj-C type
 *				 based on the MySQL value type.
 *
 * @returns: A single Obj-C result value based and converted from the MySQL value type.
 */
- (id)executeScalar:(NSString*)sqlCommand {
	id			 retVal = nil;
	MYSQL_RES    *result = [self _runQuery: sqlCommand];
	my_ulonglong rows;
	my_ulonglong columns;
	MYSQL_FIELD  *columnInfo;
	MYSQL_ROW	 row;
	
	if( result != NULL ) {
		rows = mysql_affected_rows( _mysql );
		columns = mysql_field_count( _mysql );
		if( rows > 0 && columns == 1 ) {
			columnInfo = mysql_fetch_field_direct( result, 0 );
			
			if( columnInfo != NULL ) {
				row = mysql_fetch_row( result );
				if( row != NULL ) {
					retVal = [MySQLResult convertToObjCValue: row[0] 
											   fromMySQLType: columnInfo->type
												   withFlags: columnInfo->flags
											   usingEncoding: [self encodingForConnection]];
				} // END if( row != NULL )

			} // END if( columnInfo != NULL )
			
		} // END if( rows > 0 && columns == 1 )
		
		mysql_free_result( result );
		
	} // END if( result != NULL )
	
	return retVal;
	
} // END METHOD: executeScalar: 

/*
 * @description: Run a SQL statement that will return one or more rows into a
 *				 enumerable result set for simple access.
 *
 * @returns: A MySQLResult object that conforms to the NSEnumerator class
 */
- (MySQLResult*)executeQuery:(NSString*)sqlCommand {
	MYSQL_RES   *result = [self _runQuery: sqlCommand];
	MySQLResult *retVal = nil;
	
	if (result != nil)
		retVal = [[MySQLResult alloc] initWithResult:result andConnection:_mysql 
									   usingEncoding: [self encodingForConnection]];
	
	return retVal;

} // END METHOD: executeQuery: 



#pragma mark -
#pragma mark Helper Query Methods

/*
 * @description: Use this method to escape strings for "safe" usage in queries
 */
- (NSString *)escapeString:(NSString *)stringToEscape {
	[self _setupIfNeeded];
	[self raiseExceptionIfNeeded];
	
	unsigned int length = [stringToEscape length];
	
	// Worst case senario is that every character needs to be escaped and +1 for NULL termination
	char newString[length * 2 + 1];
	const char *from = [stringToEscape cStringUsingEncoding: [self encodingForConnection]];
	
	// Let's escape the string then...
	mysql_real_escape_string( _mysql, newString, from, length );
	
	NSString *retVal = [NSString stringWithCString: newString encoding: [self encodingForConnection]];
	free( newString );
	
	return retVal;

} // END METHOD: escapeString: 

- (NSString *)escapeData:(NSData *)dataToEscape {
	[self _setupIfNeeded];
	[self raiseExceptionIfNeeded];
	
	unsigned int length = [dataToEscape length];
	
	// Worst case senario is that every character needs to be escaped and +1 for NULL termination
	char newString[length * 2 + 1];
	const char *from = [dataToEscape bytes];
	
	mysql_real_escape_string( _mysql, newString, from, length );
	
	NSString *retVal = [NSString stringWithCString: newString encoding: [self encodingForConnection]];
	free( newString );
	
	return retVal;
	
} // END METHOD: dataToEscape: 

/*
 * @description: Use this method to get a "valid" string to use in queries
 */
- (NSString *)prepareValue:(id)value {
	if( value == nil || [value class] == [NSNull class] )
		return @"NULL";
	else if( [value isKindOfClass: [NSString class]] )
		return [NSString stringWithFormat: @"'%@'", [self escapeString: value]];
	else if( [value isKindOfClass: [NSNumber class]] )
		return [NSString stringWithFormat: @"%@", value];
	else if( [value isKindOfClass: [NSDate class]] ) {
		return [NSString stringWithFormat: @"'%@'", [(NSDate *)value descriptionWithCalendarFormat: @"%Y-%m-%d %H:%M:%S"  
																						  timeZone: [NSTimeZone defaultTimeZone] 
																							locale: [NSLocale currentLocale]]];
	
	} // END else if( [value isKindOfClass: [NSDate class]] )
	else if( [value isKindOfClass: [NSCalendarDate class]] ) {
		return [NSString stringWithFormat: @"'%@'", [(NSCalendarDate *)value descriptionWithCalendarFormat: @"%Y-%m-%d %H:%M:%S"  
															 timeZone: [NSTimeZone defaultTimeZone] 
															   locale: [NSLocale currentLocale]]];

	} // END else if( [value isKindOfClass: [NSCalendarDate class]] )
	else if( [value isKindOfClass: [NSData class]] ) {
		return [NSString stringWithFormat: @"'%@'", [self escapeData: value]];
		
	} // END else if( [value isKindOfClass: [NSData class]] )
	
	
	// Default, throw a error notification
	@throw [NSException exceptionWithName: @"Unknown Value Exception" 
								   reason: [NSString stringWithFormat: @"The value %@ is not supported. (%@)", [value description], [value class]] 
								 userInfo: value];
	
} // END METHOD: prepareValue: 



#pragma mark -
#pragma mark Query Helpers
/*
 * @description: This method will perform a actual query using the MYSQL struct.
 *				 It will make sure that the connection is open and ready to perform the query.
 */
- (MYSQL_RES *)_runQuery:(NSString *)sql {
	[self open];
	
	if( mysql_query( _mysql, [sql cStringUsingEncoding: [self encodingForConnection]] ) != 0 )
		return NULL;
	
	return mysql_store_result( _mysql );
	
} // END METHOD: _runQuery:

#pragma mark -
#pragma mark MySQL Specific method IMPLEMENTATION
/*
 * @description: Will perform init operations for the current connection if needed.
 */
- (BOOL)_setupIfNeeded {
	if( _mysql == NULL ) {
		// Setup MySQL, also note that this will allocated memory by calling mysql_server_init()
		// so when closing the connection, mysql_server_end() must be manually called for complete cleanup.
		// See: mysql.h line:387 for more info about it's internal memory handling.
		_mysql = mysql_init( NULL );
		
	} // END if( _mysql == NULL )
	
	// Valid?
	if( !_mysql )
		return FALSE;
	
	// Append options for the connection.
	mysql_options( _mysql, MYSQL_READ_DEFAULT_GROUP, "LibMySQL" );
	mysql_options( _mysql, MYSQL_OPT_CONNECT_TIMEOUT, &timeout );
	
	return TRUE;
	
} // END METHOD: _setupIfNeeded:

/*
 * @description: Will close the connection if it's open and set it to NULL.
 */
- (BOOL)_teardownIfNeeded {
	if( _mysql ) {
		// Close connection
		mysql_close( _mysql );
		
		// Make sure to free memory allocated from the mysql_init() call...
		// See: mysql.h line:387 for more info.
		mysql_server_end();
		
		_mysql = NULL;
		
	} // END if( _mysql )
	
	return TRUE;
	
} // END METHOD: _teardownIfNeeded:

/*
 * @description: Performs the actual connect handling with the mysql database.
 */
- (BOOL) _realConnect {
	_mysql = mysql_real_connect(_mysql, 
							    [hostname UTF8String],
								[username UTF8String],
								[password UTF8String],
								[database UTF8String],
								port,
								(port > 0 ? NULL : ((socket != nil && [socket length] > 0) ? [socket UTF8String] : MySQLConnection_ClientSocketDefaultPath)),
								connectionProperties);
	if(!_mysql) {
		_mysql = NULL;
		@throw [[NSException alloc] initWithName: @"Connection Exception" 
										  reason: @"Unable to establish a connection to the mysql server" 
										userInfo: [NSDictionary dictionaryWithObject: self forKey: @"source"]];
		return FALSE;
		
	} // END if (!_mysql)
	
	// Set charset if present
	if( charset != nil && [charset length] > 0 )
		mysql_set_character_set( _mysql, [charset UTF8String] );
	else
		mysql_set_character_set( _mysql, [MySQLConnection_DefaultCharset UTF8String] );
	
	return TRUE;
	
} // END METHOD: _realConnect:


/*
 * @description: Generates a MySQLException object based on the current connection's
 *				 error state.
 *
 * @returns: A MySQLException object if the connection have any errors; Otherwise it will return nil.
 */
- (MySQLException*)getExceptionWithCurrentState {
	// Check mysql's error state
	int errorNumber = mysql_errno( _mysql );
	if( errorNumber != 0 ) {
		return [[MySQLException alloc] initWithName:@"MySQLException"
											 reason:@"Unable to perform MySQL action."
										 mysqlError:[NSString stringWithUTF8String: mysql_error(_mysql)]
								andMySQLErrorNumber:errorNumber];
	
	} // END if( errorNumber != 0 )
	
	return nil;
	
} // END METHOD: getExceptionWithCurrentState: 

- (void)raiseExceptionIfNeeded {
	MySQLException *error = [self getExceptionWithCurrentState];
	if( error != nil )
		@throw error;

} // END METHOD: raiseExceptionIfNeeded: 



#pragma mark -
#pragma mark Debug helpers
/*
 * @description: Will return information about this MySQLConnection object.
 *				 Useful with NSLog( ... ) for ex.
 */
- (NSString *)description {
	NSMutableString *retVal = [[NSMutableString alloc] init];
	[retVal appendString: @"\n\n-----------------------------------MySQLConnection\n-----------------------------------\n"];
	
	// Credentials.
	[retVal appendFormat: @"Credentials\n-----------------------------------\nHost: %@\nPort: %li\nSocket: %@\nUsername: %@\nPassword: <*****>", 
	 hostname, port, socket, username];
	
	[retVal appendFormat: @"\n\nClient Info: \n-----------------------------------\n%@", [self clientInformation]];
	if( [self isOpen] ) {
		[retVal appendFormat: @"\n\nServer Info: \n-----------------------------------\n%@", [self serverInformation]];
	}
	else {
		[retVal appendString: @"\n\nConnection not open..."];
	}
	
	return retVal;

} // END METHOD: description: 









@end